# -*- coding:utf-8 -*-
__author__ = 'Dragon Sun'
__date__ = '2025-06-25 07:19:53'

import datetime
from pathlib import Path
from typing import Optional, Union

from PySide6.QtCore import QObject, Signal, Qt, QPoint
from PySide6.QtGui import QTextCursor, QAction
from PySide6.QtWidgets import QWidget, QGridLayout, QPlainTextEdit, QMessageBox
from apscheduler.schedulers.background import BackgroundScheduler
from dsOKXv5.data_types import 交易方向枚举
from dsOKXv5.models import 持仓模型类
from dsOKXv5.utils.交易 import 计算预估可交易张数
from dsPyLib.utils.日期时间 import 获取时长字符串
from dsPySide6.utils.func import load_ui
from dsQtLib.指标.K线 import 为锤头线, 为倒锤头线, 放量
from dsQtLib.指标.缠论 import 缠论类, 缠论分型类
from dsQtLib.策略.决策.决策_背离 import 决策_背离
from dsQtLib.策略.决策.止损_MACD段 import 止损信息_MACD段
from dsQtLib.策略.数据.数据_K线信息 import K线信息数据准备
from dsQtLib.策略.数据.数据_MACD import MACD数据准备
from dsQtLib.策略.数据.数据_RSI import RSI数据准备
from dsQtLib.策略.数据.数据_VEGAS import VEGAS数据准备
from dsQtLib.策略.模型 import 止损价信息类
from pandas import DataFrame

from src.conf.conf import G_应用配置, G_最大单量, G_交易模式
from src.helpers.helper_db import 获取近期K线
from src.helpers.helper_ui import 显示错误对话框
from src.rpc.rpc_client_okxv5 import RPCClientOKXv5
from src.sounds.提示音 import 行情提示音枚举, 播放操作提示音, 播放行情提示音, 播放行情列表提示音
from src.views.交易视图.交易视图控制器 import 交易视图控制器类, 值守平仓方式枚举
from src.views.持仓视图.持仓视图控制器 import 持仓视图控制器类
from src.views.提示音设置视图.提示音设置视图控制器 import 提示音设置视图控制器类
from src.wss.ds_wss_okxv5 import DSOKXv5WebSocket


class 主窗口视图控制器类(QObject):
    _信号_持仓变化 = Signal()
    _信号_最新价格变化 = Signal(float)  # 最新价格
    _信号_标记价格变化 = Signal(float)  # 标记价格
    _信号_余额变化 = Signal(float)  # USDT余额
    _信号_更新K线 = Signal(DataFrame)  # K线
    _信号_更新标题 = Signal()

    # noinspection PyUnresolvedReferences
    def __init__(self, 调度器: BackgroundScheduler):
        super().__init__()

        # 传入参数
        self.调度器 = 调度器

        # 内部参数声明
        self._开始运行时间 = datetime.datetime.now()
        self._自动值守中: bool = False
        self._自动值守平仓方式: set[值守平仓方式枚举] = set()
        self._最新价格: Optional[float] = None
        self._余额: Optional[float] = None
        self._持仓: Optional[持仓模型类] = None
        self._K线: Optional[DataFrame] = None
        self._MACD原始段: Optional[DataFrame] = None
        self._MACD合并段: Optional[DataFrame] = None
        self._做多止损信息: Optional[止损价信息类] = None
        self._做空止损信息: Optional[止损价信息类] = None

        # 视图
        视图文件名 = Path(__file__).resolve().parent / '主窗口.ui'
        self.视图 = load_ui(ui_file=视图文件名, custom_widgets=[], parent=None)

        # 组件映射
        self._上部容器: QWidget = self.视图.TopContainer
        self._提示音设置视图容器: QWidget = self.视图.TiShiYinContainer
        self._提示音设置视图容器布局对象: QGridLayout = self.视图.TiShiYinLayout
        self._交易视图容器: QWidget = self.视图.JiaoYiContainer
        self._交易视图容器布局对象: QGridLayout = self.视图.JiaoYiLayout
        self._持仓视图容器: QWidget = self.视图.ChiCangContainer
        self._持仓视图容器布局对象: QGridLayout = self.视图.ChiCangLayout
        self._日志容器: QWidget = self.视图.LogContainer
        self._日志文本框: QPlainTextEdit = self.视图.LogEdit

        # 组件初始化设置
        self._上部容器.setStyleSheet('')
        self._提示音设置视图容器.setStyleSheet('')
        self._交易视图容器.setStyleSheet('')
        self._持仓视图容器.setStyleSheet('')
        self._日志容器.setStyleSheet('')
        self._日志文本框.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
        self._日志文本框.customContextMenuRequested.connect(self._显示日志文本框弹出菜单)

        # 视图控制器
        self.提示音设置视图控制器 = 提示音设置视图控制器类()
        self.交易视图控制器 = 交易视图控制器类()
        self.持仓视图控制器 = 持仓视图控制器类(调度器=调度器)
        self._提示音设置视图容器布局对象.addWidget(self.提示音设置视图控制器.视图)
        self._交易视图容器布局对象.addWidget(self.交易视图控制器.视图)
        self._持仓视图容器布局对象.addWidget(self.持仓视图控制器.视图)

        # 视图控制器回调
        self.交易视图控制器.吃单处理 = self._回调_吃单
        self.交易视图控制器.市价全平处理 = self._回调_全平
        self.交易视图控制器.反手处理 = self._回调_反手
        self.交易视图控制器.设置止损处理 = self._回调_设置止损
        self.交易视图控制器.设置止盈处理 = self._回调_设置止盈
        self.交易视图控制器.自动值守处理 = self._回调_自动值守

        # 信号绑定
        self._信号_持仓变化.connect(self._信号处理_持仓变化处理)
        self._信号_最新价格变化.connect(self._信号处理_最新价格变化处理)
        self._信号_标记价格变化.connect(self._信号处理_标记价格变化处理)
        self._信号_余额变化.connect(self._信号处理_余额变化处理)
        self._信号_更新K线.connect(self._信号处理_更新K线)
        self._信号_更新标题.connect(self._信号处理_更新标题)

        # RPC
        self._rpc_client: RPCClientOKXv5 = RPCClientOKXv5(显示日志=False)

        # 调度器 (都是异步函数，内部更新界面需要用信号)
        self.调度器.add_job(func=self._调度器_获取K线, trigger='cron', minute='*/3', second='2', coalesce=True, max_instances=5)  # 三分钟周期的K线
        self.调度器.add_job(func=self._调度器_更新标题, trigger='cron', second='*', coalesce=True, max_instances=5)

        # WebSocket (都是异步函数，内部更新界面需要用信号)
        self._wss = DSOKXv5WebSocket()
        self._wss.信号_当前持仓变化.connect(self._wss_持仓变化处理)
        self._wss.信号_最新价格更新.connect(self._wss_最新价格变化处理)
        self._wss.信号_标记价格更新.connect(self._wss_标记价格变化处理)
        self._wss.信号_收到USDT余额.connect(self._wss_余额变化处理)
        self._wss.运行()

    def 刷新(self):
        # ①. 重置组件状态
        self.交易视图控制器.传递近期K线(K线=None)
        self.交易视图控制器.传递持仓(持仓=None)
        self.交易视图控制器.传递最大下单量(最大下单量=G_最大单量)

        self.持仓视图控制器.传递持仓(持仓=None)
        self.持仓视图控制器.传递标记价格(标记价格=0)

        self._日志文本框.clear()

        # ②. 获取最新K线(直接调用调度器任务)
        self._调度器_获取K线()

        # ③. 获取当前持仓
        self._获取持仓处理()

    # ---------------------------------------- 交易视图回调 ---------------------------------------- #

    def _回调_吃单(self, 交易方向: 交易方向枚举, 交易数量: float) -> Union[Optional[持仓模型类], Exception]:
        self.log('吃单......')
        try:
            # 如果是加仓，则交易方向需要与持仓一致
            # 如果是加仓，不能指定止盈止损，否则会报错
            if self._持仓:
                交易方向 = self._持仓.方向
                止损触发价 = 0
            else:
                # 获取止损触发价
                止损触发价 = self._获取止损触发价(交易方向=交易方向)
                if isinstance(止损触发价, Exception):
                    raise 止损触发价

            # 开仓
            持仓 = self._rpc_client.吃单(
                产品=G_应用配置.标的, 方向=交易方向, 数量=交易数量, 杠杆=G_应用配置.杠杆倍数, 止盈触发价=0, 止损触发价=止损触发价
            )
            if isinstance(持仓, Exception):
                raise 持仓

            # 成功
            self._更新当前持仓(持仓=持仓)
            self._操作成功处理()
            return 持仓
        except Exception as e:
            self._操作错误处理(错误=e)
            return e

    def _回调_全平(self) -> Optional[Exception]:
        self.log('全平......')
        try:
            # 市价全平
            result = self._rpc_client.市价全平()
            if isinstance(result, Exception):
                raise result

            # 成功
            self._更新当前持仓(持仓=None)
            self._操作成功处理()
            return result
        except Exception as e:
            self._操作错误处理(错误=e)
            return e

    def _回调_反手(self) -> Union[Optional[持仓模型类], Exception]:
        self.log('反手......')
        try:
            # 获取止损触发价
            交易方向 = 交易方向枚举.多 if self._持仓.方向 == 交易方向枚举.空 else 交易方向枚举.空
            止损触发价 = self._获取止损触发价(交易方向=交易方向)
            if isinstance(止损触发价, Exception):
                raise 止损触发价

            # 反手
            持仓 = self._rpc_client.反手(止盈触发价=0, 止损触发价=止损触发价)
            if isinstance(持仓, Exception):
                raise 持仓

            # 成功
            self._更新当前持仓(持仓=持仓)
            self._操作成功处理()
            return 持仓
        except Exception as e:
            self._操作错误处理(错误=e)
            return e

    def _回调_设置止损(self, 止损触发价: float) -> Union[Optional[持仓模型类], Exception]:
        self.log('设置止损......')
        try:
            # 操作确认
            # if not self._显示确认对话框(f'请仔细检查：要设置的 止损触发价={止损触发价}?'):
            #     raise ValueError('用户取消')

            # 如果设置值与当前值一致，则不予设置
            if self._持仓.止盈止损单 and 止损触发价 == self._持仓.止盈止损单.止损触发价:
                raise ValueError('没有变化，不予设置')

            # 修改止损触发价
            持仓 = self._rpc_client.修改止损触发价(止损触发价=止损触发价)
            if isinstance(持仓, Exception):
                raise 持仓

            # 成功
            self._更新当前持仓(持仓=持仓)
            self._操作成功处理()
            return 持仓
        except Exception as e:
            self._操作错误处理(错误=e)
            return e

    def _回调_设置止盈(self, 止盈触发价: float) -> Union[Optional[持仓模型类], Exception]:
        self.log('设置止盈......')
        try:
            # 操作确认
            # if not self._显示确认对话框(f'请仔细检查：要设置的 止盈触发价={止盈触发价}?'):
            #     raise ValueError('用户取消')

            # 如果设置值与当前值一致，则不予设置
            if self._持仓.止盈止损单 and 止盈触发价 == self._持仓.止盈止损单.止盈触发价:
                raise ValueError('没有变化，不予设置')

            # 修改止盈触发价
            持仓 = self._rpc_client.修改止盈触发价(止盈触发价=止盈触发价)
            if isinstance(持仓, Exception):
                raise 持仓

            # 成功
            self._更新当前持仓(持仓=持仓)
            self._操作成功处理()
            return 持仓
        except Exception as e:
            self._操作错误处理(错误=e)
            return e

    def _回调_自动值守(self, 值守状态: bool, 平仓方式: set[值守平仓方式枚举]) -> Optional[Exception]:
        操作描述 = '开始' if 值守状态 else '停止'
        self.log(f'{操作描述} 自动值守...')
        self._自动值守中 = 值守状态
        self._自动值守平仓方式 = 平仓方式
        self._操作成功处理()
        return None  # 未持仓时，开始自动值守会失败，但是 交易窗口 判断了这种情况，所以这里就不用判断了

    # ---------------------------------------- 信号处理 ---------------------------------------- #

    def _信号处理_持仓变化处理(self):
        self._获取持仓处理()

    def _信号处理_最新价格变化处理(self, 最新价格: float):
        self._最新价格 = 最新价格
        self._信号处理_更新标题()

    def _信号处理_标记价格变化处理(self, 标记价格: float):
        self.持仓视图控制器.传递标记价格(标记价格=标记价格)

    def _信号处理_余额变化处理(self, 余额: float):
        self._余额 = 余额
        self._信号处理_更新标题()

        # 更新交易视图最大下单量
        if self._最新价格:
            最大可买张数 = G_最大单量 - self._持仓.持仓量 if self._持仓 else G_最大单量
            可买张数 = 计算预估可交易张数(资金量=余额, 成交均价=self._最新价格, 杠杆倍数=G_应用配置.杠杆倍数)
            可买张数 = min(最大可买张数, 可买张数)
            self.交易视图控制器.传递最大下单量(最大下单量=可买张数)

    def _信号处理_更新K线(self, K线: DataFrame):
        # self.log('K线更新完毕')

        # 获取K线
        K线 = K线信息数据准备(K线=K线)  # 添加额外K线数据
        if (K线 is None) or (len(K线) < 2):
            self.log(f'未能正确获取K线(为None或者长度小于2)')
            if self.提示音设置视图控制器.异常提示音状态:
                播放行情提示音(提示音=行情提示音枚举.异常)
            return

        # 验证K线完整性
        当前时间槽 = G_应用配置.K线周期.时间转时间槽(时间=datetime.datetime.now(), 偏移=-1)
        K线最新时间槽 = K线['时间槽'].iloc[-1]
        if 当前时间槽 != K线最新时间槽:
            self.log(f'获取的K线不正确(时间槽对不上)')
            if self.提示音设置视图控制器.异常提示音状态:
                播放行情提示音(提示音=行情提示音枚举.异常)
            return

        # 添加必要数据
        K线 = RSI数据准备(策略K线=K线)  # 添加RSI数据
        MACD段 = MACD数据准备(策略K线=K线)  # 添加MACD数据
        K线 = VEGAS数据准备(K线=K线)
        K线['放量'] = 放量(成交量=K线['成交量'].values)

        # 记录
        self._K线 = K线
        self._MACD原始段 = MACD段['原始MACD段']
        self._MACD合并段 = MACD段['合并MACD段']

        # 更新止损信息
        self._做多止损信息 = 止损信息_MACD段(策略K线=self._K线, 合并MACD段=self._MACD合并段, 交易方向=交易方向枚举.多)
        self._做空止损信息 = 止损信息_MACD段(策略K线=self._K线, 合并MACD段=self._MACD合并段, 交易方向=交易方向枚举.空)

        # 各种行情处理
        self._行情提示音处理()

        # 自动值守处理
        if self._自动值守中 and self._持仓:
            self._自动值守处理()

        # 更新交易视图K线列表
        self.交易视图控制器.传递近期K线(K线=self._K线)

    def _信号处理_更新标题(self):
        时长字符串 = 获取时长字符串(开始时间=self._开始运行时间)
        余额字符串 = f'{self._余额:.2f}' if self._余额 else 'None'
        窗口标题 = f'虚拟币自主交易器 - {时长字符串} - {G_交易模式.name} - 余额：{余额字符串} - 最新价格：{self._最新价格}'
        self.视图.setWindowTitle(窗口标题)

    # ---------------------------------------- WebSocket ---------------------------------------- #

    def _wss_持仓变化处理(self):
        self._信号_持仓变化.emit()

    def _wss_最新价格变化处理(self, 最新价格: float):
        self._信号_最新价格变化.emit(最新价格)

    def _wss_标记价格变化处理(self, 标记价格: float):
        self._信号_标记价格变化.emit(标记价格)

    def _wss_余额变化处理(self, 余额: float):
        self._信号_余额变化.emit(余额)

    # ---------------------------------------- 计时器 ---------------------------------------- #

    def _调度器_获取K线(self):
        K线 = 获取近期K线()
        self._信号_更新K线.emit(K线)  # 这是调度器处理函数，在线程中的，要更新界面必须使用信号系统

    def _调度器_更新标题(self):
        self._信号_更新标题.emit()  # 这是调度器处理函数，在线程中的，要更新界面必须使用信号系统

    # ---------------------------------------- 其它处理 ---------------------------------------- #

    def _更新当前持仓(self, 持仓: Optional[持仓模型类]):
        self._持仓 = 持仓
        self.交易视图控制器.传递持仓(持仓=持仓)
        self.持仓视图控制器.传递持仓(持仓=持仓)

    def _获取持仓处理(self):
        # self.log('获取当前持仓......')
        持仓 = self._rpc_client.获取当前持仓()
        if isinstance(持仓, Exception):
            self.log(f'获取当前持仓失败：{持仓}', 同一行追加=True)
        else:
            # self.log('完成', 同一行追加=True)
            self._更新当前持仓(持仓=持仓)

    def _获取止损触发价(self, 交易方向: 交易方向枚举) -> Union[float, Exception]:
        # 获取止损信息
        止损信息 = self._做多止损信息 if 交易方向 == 交易方向枚举.多 else self._做空止损信息
        if 止损信息 is None:
            错误信息 = '没有止损信息'
        else:
            # 止损信息验证
            if not 止损信息.有效():
                错误信息 = '止损信息无效'
            else:
                # 最新价格判断
                if self._最新价格 is None:
                    错误信息 = '没有最新价格'
                else:
                    # 止损触发价和最新价格的关系
                    止损触发价 = 止损信息.止损价
                    止损触发价 = (止损触发价 - 0.1) if 交易方向 == 交易方向枚举.多 else (止损触发价 + 0.1)
                    # 做多，止损触发价应小于开仓价; 做空，止损触发价应大于开仓价
                    止损触发价合理 = (止损触发价 < self._最新价格) if (交易方向 == 交易方向枚举.多) else (止损触发价 > self._最新价格)
                    if not 止损触发价合理:
                        错误信息 = '止损触发价不合理'
                    else:
                        return 止损触发价

        # 走到这里说明出错了
        return ValueError(错误信息)

    def _自动值守处理(self):
        # MACD段移动止损
        if 值守平仓方式枚举.MACD段移动止损 in self._自动值守平仓方式:
            当前止损 = self._做多止损信息 if self._持仓.方向 == 交易方向枚举.多 else self._做空止损信息
            当前止损价 = 当前止损.止损价
            持单止损价 = self._持仓.止盈止损单.止损触发价
            做多变更 = ((self._持仓.方向 == 交易方向枚举.多) and (当前止损价 > 持单止损价))
            做空变更 = ((self._持仓.方向 == 交易方向枚举.空) and (当前止损价 < 持单止损价))
            需要变更 = 做多变更 or 做空变更
            self.log(f'自动值守，持单止损价={持单止损价}, 当前止损价：{当前止损价}')
            if 需要变更:
                新持仓 = self._回调_设置止损(止损触发价=当前止损价)
                操作结果 = not isinstance(新持仓, Exception)
                操作结果字符串 = '成功' if 操作结果 else '失败'
                self.log(f', ......变更 {操作结果字符串}', 同一行追加=True)

        # 背离平仓
        if 值守平仓方式枚举.背离 in self._自动值守平仓方式:
            决策项目 = 决策_背离(策略K线=self._K线, 原始MACD段=self._MACD原始段, 交易方向=self._持仓.方向)
            self.log(f'背离检查，')
            if 决策项目.结果:
                错误 = self._回调_全平()
                操作结果 = 错误 is None
                操作结果字符串 = '成功' if 操作结果 else '失败'
                self.log(f'出现，......平仓 {操作结果字符串}')
            else:
                self.log(f'未出现', 同一行追加=True)

    def _操作错误处理(self, 错误: Exception):
        错误信息 = f'{错误}'
        播放操作提示音(成功=False)
        self.log(f'失败：{错误信息}', 同一行追加=True)
        显示错误对话框(错误信息)

    def _操作成功处理(self):
        播放操作提示音(成功=True)
        self.log('成功', 同一行追加=True)

    @staticmethod
    def _显示确认对话框(提示信息: str) -> bool:
        对话框 = QMessageBox()
        对话框.setWindowTitle('确认')
        对话框.setText(f'{提示信息}')
        对话框.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
        对话框.setDefaultButton(QMessageBox.StandardButton.No)

        # 显示对话框并获取用户的选择
        result = 对话框.exec()

        if result == QMessageBox.StandardButton.Yes:
            return True
        else:
            return False

    def _行情提示音处理(self):
        if self._K线 is None:
            return

        提示音列表 = []
        倒数K线1 = self._K线.iloc[-1]
        倒数K线2 = self._K线.iloc[-2]

        # 已获取
        if self.提示音设置视图控制器.已获取提示音状态:
            提示音列表.append(行情提示音枚举.已获取)

        # 顺向/逆向
        if self._持仓:
            顺向_多 = ((self._持仓.方向 == 交易方向枚举.多) and (倒数K线1['K线类型'] == 1))
            顺向_空 = ((self._持仓.方向 == 交易方向枚举.空) and (倒数K线1['K线类型'] == -1))
            逆向_多 = ((self._持仓.方向 == 交易方向枚举.多) and (倒数K线1['K线类型'] == -1))
            逆向_空 = ((self._持仓.方向 == 交易方向枚举.空) and (倒数K线1['K线类型'] == 1))
            if self.提示音设置视图控制器.顺向提示音状态 and (顺向_多 or 顺向_空):
                提示音列表.append(行情提示音枚举.顺向)
            if self.提示音设置视图控制器.逆向提示音状态 and (逆向_多 or 逆向_空):
                提示音列表.append(行情提示音枚举.逆向)

        # 放量(是前一根K线成交量的2倍以上)
        if self.提示音设置视图控制器.放量提示音状态 and 倒数K线1['放量']:
            提示音列表.append(行情提示音枚举.放量)

        # 大K线(振幅>0.2%)
        if self.提示音设置视图控制器.大K线提示音状态 and (倒数K线1['振幅'] > 0.2):
            提示音列表.append(行情提示音枚举.大K线)

        # 锤头线
        if self.提示音设置视图控制器.锤头线提示音状态:
            锤头线 = 为锤头线(开盘价=倒数K线1['开盘价'], 最高价=倒数K线1['最高价'], 最低价=倒数K线1['最低价'], 收盘价=倒数K线1['收盘价'])
            倒锤头线 = 为倒锤头线(开盘价=倒数K线1['开盘价'], 最高价=倒数K线1['最高价'], 最低价=倒数K线1['最低价'], 收盘价=倒数K线1['收盘价'])
            if (锤头线 or 倒锤头线) and (倒数K线1['振幅'] > 0.15):  # K线的振幅要 > 0.15%
                提示音列表.append(行情提示音枚举.锤头线)

        # 跨均线
        均线 = 倒数K线1['EMA12']
        跨均线 = (倒数K线1['柱体低值'] < 均线 < 倒数K线1['柱体高值'])
        if self.提示音设置视图控制器.跨均线提示音状态 and 跨均线:
            提示音列表.append(行情提示音枚举.跨均线)

        # 分型
        if self.提示音设置视图控制器.分型提示音状态:
            缠论K线模型列表 = 缠论类.原始K线转缠论K线(原始K线=self._K线)
            缠论分型模型列表 = 缠论类.获取缠论分型列表(缠论K线=缠论K线模型列表)
            缠论笔分型模型列表 = 缠论类.获取缠论笔分型列表(分型列表=缠论分型模型列表)
            缠论笔分型数据帧 = 缠论分型类.模型列表转数据帧(模型列表=缠论笔分型模型列表)
            最后分型结束时间 = 缠论笔分型数据帧['结束时间槽'].iloc[-1]
            if 最后分型结束时间 == 倒数K线1['时间槽']:
                提示音列表.append(行情提示音枚举.分型)

        # RSI超买超卖
        if self.提示音设置视图控制器.RSI超买超卖提示音状态:
            超买 = 倒数K线1['RSI'] > 70.0
            超卖 = 倒数K线1['RSI'] < 30.0
            if 超买 or 超卖:
                提示音列表.append(行情提示音枚举.RSI超买超卖)

        # MACD金死叉
        if self.提示音设置视图控制器.MACD金叉提示音状态 and (倒数K线1['MACD交叉'] == 1):
            提示音列表.append(行情提示音枚举.MACD金叉)
        if self.提示音设置视图控制器.MACD死叉提示音状态 and (倒数K线1['MACD交叉'] == -1):
            提示音列表.append(行情提示音枚举.MACD死叉)

        # 顶底背离
        顶背离决策项目 = 决策_背离(策略K线=self._K线, 原始MACD段=self._MACD原始段, 交易方向=交易方向枚举.多)
        底背离决策项目 = 决策_背离(策略K线=self._K线, 原始MACD段=self._MACD原始段, 交易方向=交易方向枚举.空)
        if self.提示音设置视图控制器.MACD顶背离提示音状态 and 顶背离决策项目.结果:
            提示音列表.append(行情提示音枚举.MACD顶背离)
        if self.提示音设置视图控制器.MACD底背离提示音状态 and 底背离决策项目.结果:
            提示音列表.append(行情提示音枚举.MACD底背离)

        # 破上下轨
        上轨 = self.提示音设置视图控制器.上轨数值
        下轨 = self.提示音设置视图控制器.下轨数值
        if self.提示音设置视图控制器.破上轨提示音状态 and 上轨:
            破上轨 = (倒数K线2['最高价'] < 上轨) and (倒数K线1['最高价'] > 上轨)
            if 破上轨:
                提示音列表.append(行情提示音枚举.破上轨)
        if self.提示音设置视图控制器.破下轨提示音状态 and 下轨:
            破下轨 = (倒数K线2['最低价'] > 下轨) and (倒数K线1['最低价'] < 下轨)
            if 破下轨:
                提示音列表.append(行情提示音枚举.破下轨)

        # 播放行情列表提示音
        if not self.提示音设置视图控制器.静音状态:
            播放行情列表提示音(提示音列表=提示音列表)

    def _显示日志文本框弹出菜单(self, point: QPoint):
        弹出菜单 = self._日志文本框.createStandardContextMenu()

        # 添加菜单项
        清除所有日志菜单项 = QAction('清除所有日志')
        清除所有日志菜单项.triggered.connect(self._清除所有日志处理)
        弹出菜单.addSeparator()
        弹出菜单.addAction(清除所有日志菜单项)

        弹出菜单.exec(self._日志文本框.mapToGlobal(point))

    def _清除所有日志处理(self):
        self._日志文本框.clear()

    def log(self, text: str, 同一行追加: bool = False):
        if 同一行追加:
            cursor = self._日志文本框.textCursor()
            cursor.movePosition(QTextCursor.MoveOperation.End)  # 移动到文档末尾
            cursor.movePosition(QTextCursor.MoveOperation.StartOfLine)  # 移动到当前行的开头
            cursor.movePosition(QTextCursor.MoveOperation.EndOfLine)  # 移动到当前行的末尾
            self._日志文本框.setTextCursor(cursor)
            self._日志文本框.insertPlainText(text)  # 在当前行末尾插入文本
        else:
            当前时间 = datetime.datetime.now()
            当前时间字符串 = 当前时间.strftime('%Y-%m-%d %H:%M:%S')
            self._日志文本框.appendPlainText(f'{当前时间字符串} {text}')
